﻿using System;
using System.Collections.Generic;
using NGinnBPM.Lib.Exceptions;
using System.Text;
using System.Runtime.Serialization;
using NGinnBPM.Lib.Data;

namespace NGinnBPM.Lib.Schema
{
    /// <summary>
    /// Definition of default error behavior for composite task
    /// </summary>
    public enum ErrorBehaviorType
    {
        WaitForUserAction,
        BubbleUp
    }
    /// <summary>
    /// Composite task - task containing other tasks. Behaves like inline subprocess.
    /// </summary>
    [Serializable]
    [DataContract]
    public class CompositeTask : Task
    {
        //[IgnoreDataMember]
        //private Dictionary<string, NetNode> _nodes = new Dictionary<string, NetNode>();
        
        public CompositeTask()
        {
            TaskType = NGinnTaskType.Composite;
            Tasks = new List<Task>();
            Places = new List<Place>();
            Flows = new List<Flow>();
        }

        private ProcessDefinition _parentPd;

        [IgnoreDataMember]
        public override ProcessDefinition ParentProcessDefinition
        {
            get
            {
                return _parentPd;
            }
        }

       

        [DataMember]
        public List<Place> Places { get; set; }
        
        [DataMember]
        public List<Task> Tasks { get; set; }
        
        [DataMember]
        public List<Flow> Flows { get; set; }

        [IgnoreDataMember]
        public IList<NetNode> Nodes
        {
            get
            {
                List<NetNode> lst = new List<NetNode>();
                foreach (NetNode nn in Tasks)
                    lst.Add(nn);
                foreach (NetNode nn in Places)
                    lst.Add(nn);
                return lst;
            }
        }

        public void AddPlace(Place pl)
        {
            if (pl.Id == null || pl.Id.Length == 0) throw new Exception("Missing place ID");
            if (GetNodeRecursive(pl.Id) != null)
                throw new Exception("Node already defined: " + pl.Id);
            pl.InitSchema(this, this.ParentProcessDefinition);
            Places.Add(pl);
        }

        public void AddTask(Task tsk)
        {
            if (tsk.Id == null || tsk.Id.Length == 0) throw new Exception("Missing task ID");
            if (GetNodeRecursive(tsk.Id) != null)
                throw new Exception("Node already exists: " + tsk.Id);
            tsk.InitSchema(this, this.ParentProcessDefinition);
            Tasks.Add(tsk);
        }

        /// <summary>
        /// Composite's start place
        /// </summary>
        [IgnoreDataMember]
        public Place StartPlace
        {
            get 
            {
                return Places.Find(x => x.PlaceType == PlaceTypes.Start);
            }
        }


        

        public void AddFlow(Flow t)
        {
            if (string.IsNullOrEmpty(t.IdFrom) || string.IsNullOrEmpty(t.IdTo)) throw new Exception("Flow must have start and target node");
            NetNode p = GetNode(t.IdFrom);
            if (p == null) throw new Exception("Node not defined: " + t.From.Id);
            NetNode q = GetNode(t.IdTo);
            if (q == null) throw new Exception("Node not defined: " + t.To.Id);
            t.From = p;
            t.To = q;
            if (t.IsCancelling || t.SourcePort != TaskOutPortType.Default)
            {
                if (t.InputCondition != null && t.InputCondition.Length > 0)
                    throw new NGinnBPM.Lib.Exceptions.ProcessDefinitionException(ParentProcessDefinition.Name, t.From.Id, "InputCondition not allowed");
            }

            if (p is Place && q is Place) throw new Exception("Flow cannot connect two places");
            if (p is Task && q is Task)
            {
                //adding implicit place between p and q
                Task tq = q as Task;
                Task tp = p as Task;
                Place ptran = new Place();
                ptran.IsImplicit = true;
                ptran.Id = string.Format("{0}.-.{1}",  tp.Id, tq.Id);
                int cnt = 1;
                while (GetNode(ptran.Id) != null)
                {
                    ptran.Id = string.Format("{0}.-{2}-.{1}", tp.Id, tq.Id, cnt++);
                }
                AddPlace(ptran);
                t.To = ptran; //change target node to the implicit place
                Flow f2 = new Flow();
                f2.From = ptran;
                f2.To = tq;
                AddFlow(t);
                AddFlow(f2);
            }
            else
            {
                Flows.Add(t);
            }
        }

        public NetNode GetNode(string nodeId)
        {
            NetNode n = Tasks.Find(x => x.Id == nodeId);
            if (n != null) return n;
            n = Places.Find(x => x.Id == nodeId);
            return n;
        }

        public Task GetTask(string taskId)
        {
            return (Task)GetNode(taskId);
        }

        public Task RequireTask(string taskId)
        {
            Task tsk = GetTask(taskId);
            if (tsk == null) throw new UndefinedNetNodeException(ParentProcessDefinition.DefinitionId, taskId);
            return tsk;
        }

        public Place GetPlace(string placeId)
        {
            return (Place)GetNode(placeId);
        }

        public Place RequirePlace(string placeId)
        {
            Place tsk = GetPlace(placeId);
            if (tsk == null) throw new UndefinedNetNodeException(ParentProcessDefinition.DefinitionId, placeId);
            return tsk;
        }

        public IList<NetNode> GetNodes()
        {
            return Nodes;
        }

        public IList<Task> GetTasks()
        {
            List<Task> l = new List<Task>();
            foreach (NetNode nn in GetNodes())
                if (nn is Task) l.Add((Task)nn);
            return l;
        }

        public IList<Place> GetPlaces()
        {
            List<Place> l = new List<Place>();
            foreach (NetNode nn in GetNodes())
                if (nn is Place) l.Add((Place)nn);
            return l;
        }

        /// <summary>
        /// Return all net nodes in task and its subtasks.
        /// </summary>
        /// <returns></returns>
        public IList<NetNode> GetNodesRecursive()
        {
            List<NetNode> l = new List<NetNode>();
            foreach(NetNode nn in this.Nodes)
            {
                if (nn is CompositeTask)
                    l.AddRange(((CompositeTask)nn).GetNodesRecursive());
                else
                    l.Add(nn);
            }
            return l;
        }

        /// <summary>
        /// Return list of ALL tasks in this composite, 
        /// including all nested ones
        /// </summary>
        /// <returns></returns>
        public IList<Task> GetTasksRecursive()
        {
            List<Task> l = new List<Task>();
            foreach (Task t in this.Tasks)
            {
                if (t is CompositeTask)
                    l.AddRange(((CompositeTask)t).GetTasksRecursive());
                else
                    l.Add(t);
            }
            return l;
        }
        

        /// <summary>
        /// Returns node with given Id by recursive search
        /// in all composite subtasks
        /// </summary>
        /// <param name="nodeId"></param>
        /// <returns></returns>
        public NetNode GetNodeRecursive(string nodeId)
        {
            NetNode nn = GetNode(nodeId);
            if (nn != null) return nn;
            foreach (NetNode n in Tasks)
            {
                if (n is CompositeTask)
                {
                    nn = ((CompositeTask)n).GetNodeRecursive(nodeId);
                    if (nn != null) return nn;
                }
            }
            return null;
        }

        public override bool Validate(IList<ValidationMessage> msgs)
        {
            int si = msgs.Count;
            if (StartPlace == null)
            {
                msgs.Add(new ValidationMessage(true, "No start place in composite task " + Id, this));
            }
                
            base.Validate(msgs);
            foreach (NetNode nn in GetNodes())
                nn.Validate(msgs);
            for (int i=si; i<msgs.Count; i++)
            {
                if (msgs[i].IsError) return false;
            }
            return true;
        }

        public override void InitSchema(CompositeTask parentTask, ProcessDefinition parentProcess)
        {
            base.InitSchema(parentTask, parentProcess);
            _parentPd = parentProcess;
            foreach (Task t in Tasks)
            {
                t.InitSchema(this, parentProcess);
            }
            foreach (Place p in Places)
            {
                p.InitSchema(this, parentProcess);
            }
        }

        [IgnoreDataMember]
        public override ICollection<Flow> FlowsIn
        {
            get
            {
                if (ParentTask == null)
                {
                    if (ParentProcessDefinition != null && ParentProcessDefinition.Body == this)
                        return new List<Flow>();
                }
                return base.FlowsIn;
            }
        }

        [IgnoreDataMember]
        public override ICollection<Flow> FlowsOut
        {
            get
            {
                if (ParentTask == null)
                {
                    if (ParentProcessDefinition != null && ParentProcessDefinition.Body == this)
                        return new List<Flow>();
                }
                return base.FlowsOut;
            }
        }

        public IList<Flow> GetChildNodeFlowsOut(string id)
        {
            return Flows.FindAll(x => x.IdFrom == id);
        }

        public IList<Flow> GetChildNodeFlowsIn(string id)
        {
            return Flows.FindAll(x => x.IdTo == id);
        }

        public IList<NetNode> GetChildNodeNodesOut(string id)
        {
            List<NetNode> lst = new List<NetNode>();
            foreach (Flow fl in GetChildNodeFlowsOut(id))
            {
                lst.Add(fl.To != null ? fl.To : GetNode(fl.IdTo));
            }
            return lst;
        }

        public IList<NetNode> GetChildNodeNodesIn(string id)
        {
            List<NetNode> lst = new List<NetNode>();
            foreach (Flow fl in GetChildNodeFlowsIn(id))
            {
                lst.Add(fl.From != null ? fl.From : GetNode(fl.IdFrom));
            }
            return lst;
        }

        protected static string TaskIdForXmlSchemaType(string tid)
        {
            return tid.Replace('.', '_');
        }

        public void GetAllTaskDataSchemas(Data.TypeSet types)
        {
            StructDef sd = this.GetInternalDataSchema();
            sd.Name = string.Format("Task_{0}_InternalData", TaskIdForXmlSchemaType(this.Id));
            types.AddType(sd);

            sd = this.GetInputDataSchema();
            sd.Name = string.Format("Task_{0}_InputData", TaskIdForXmlSchemaType(this.Id));
            types.AddType(sd);

            sd = this.GetOutputDataSchema();
            sd.Name = string.Format("Task_{0}_OutputData", TaskIdForXmlSchemaType(this.Id));
            types.AddType(sd);

            foreach (NetNode nn in this.GetNodesRecursive())
            {
                if (nn is Task)
                {
                    Task t = (Task)nn;
                    sd = t.GetInternalDataSchema();
                    sd.Name = string.Format("Task_{0}_InternalData", TaskIdForXmlSchemaType(t.Id));
                    types.AddType(sd);

                    sd = t.GetInputDataSchema();
                    sd.Name = string.Format("Task_{0}_InputData", TaskIdForXmlSchemaType(t.Id));
                    types.AddType(sd);

                    sd = t.GetOutputDataSchema();
                    sd.Name = string.Format("Task_{0}_OutputData", TaskIdForXmlSchemaType(t.Id));
                    types.AddType(sd);
                }
            }

        }

    }
}
